{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# Sessions in Flask FLASK中的会话\r\n",
    "\r\n",
    "会话 `Session` 是在请求之间存储用户特定数据的另一种方式。它的工作原理与 cookies 类似。若要使用会话，必须首先设置密钥。`Flask` 包的 `session` 对象用于设置和获取 `session` 数据。会话对象的工作方式类似于字典，但它也可以跟踪修改。\r\n",
    "\r\n",
    "当我们使用会话`Session`时，数据以 `cookie` 的形式存储在浏览器中。用于存储会话数据的 `cookie` 是已知的会话 cookie。但是，与普通 cookie 不同的是，Flask 对会话 cookie 进行了`加密签名`(Cryptographically signs)。这意味着任何人都可以查看 cookie 的内容，但是不能修改 cookie，除非他拥有用于签名 cookie 的密钥。这就是为什么建议将一个长而难以猜测的字符串设置为秘钥的原因。一旦设置了`Session cookie`，服务器的每个后续请求都会使用相同的密钥对其进行取消签名(unsign)，以验证 cookie 的真实性。如果 Flask 无法取消签名(unsign)这个`Session cookie`，那么Flask将被丢弃它的内容，重新生成一个新的`Session cookie`,并发送到浏览器。\r\n",
    "\r\n",
    "如果你曾用 PHP 这样的语言处理过Sessions，那么FLASK中 Sessions 就有点不一样了。在 PHP 中，`Sessions cookie` 不存储 `session data`，而只存储session id。`session id` 是 PHP 创建的唯一字符串，用于将`session data`与 cookie 关联。`session data`本身存储在服务器上的一个文件中。在收到来自客户端的请求后，PHP 使用会话 id 检索`session data`，并使其在代码中可用。这种类型的会话称为服务器端会话，而默认情况下提供的会话类型称为客户端会话。\r\n",
    "\r\n",
    "默认情况下，在 Flask中，`cookies` 和基于客户端(client-based)的`Sessions`之间没有太大的区别。因此，基于客户端的会话遭受了 cookies 所具有的相同缺陷:\r\n",
    "\r\n",
    "1. 不能存储像密码这样的敏感数据\r\n",
    "2. 每个请求都有额外的有效负载\r\n",
    "3. 不能存储超过4KB 的数据\r\n",
    "4. 限制每个网站的 cookie 数量等等\r\n",
    "\r\n",
    "Cookie 和基于客户机的会话之间唯一的真正区别是，Flask 保证会话 cookie 的内容不会被用户调整(除非他拥有密钥)。\r\n",
    "\r\n",
    "如果您想在 Flask 中使用服务器端会话，您可以编写自己的会话界面或者使用扩展，如 `Flask-Session` 和 `Flask-KVSession`。\r\n",
    "\r\n",
    "\r\n",
    "## 如何读、写和删除会话数据\r\n",
    "\r\n",
    "下面的清单演示了如何读取、写入和删除会话数据。打开 main2.py 文件，在 article () view 函数之后添加以下代码:\r\n",
    "\r\n",
    "flask_app/main2.py\r\n",
    "```python\r\n",
    "from flask import Flask,render_template,request,redirect,url_for,flash,make_response,session\r\n",
    "\r\n",
    "#...\r\n",
    "@app.route('/visits-counter/')\r\n",
    "def visits():\r\n",
    "    if 'visits' in session:\r\n",
    "        session['visit'] = session.get('visits') + 1\r\n",
    "    else:\r\n",
    "        session['visit'] = 1\r\n",
    "    return \"Total visits: {}\".format(session.get('visits'))\r\n",
    "\r\n",
    "@app.route('/delete-visits/')    \r\n",
    "def delete_visits():\r\n",
    "    session.pop('visits',None) \r\n",
    "    return 'Visits deleted'\r\n",
    "#...\r\n",
    "```\r\n",
    "\r\n",
    "注意，我们使用会话对象就像使用普通字典一样。\r\n",
    "如果服务器还没有运行，请启动服务器 `python main2.py runserver`\r\n",
    "并访问 http://127.0.0.1:5000/visits-counter/。你应该看到总访问量如下: \r\n",
    "`Total visits: 1`\r\n",
    "多刷新页面几次以增加访问次数。\r\n",
    "`Total visits: 4`\r\n",
    "\r\n",
    "只有在创建新会话或修改现有会话时，Flask 才会将会话 cookie 发送给客户端。当你第一次访问  http://localhost:5000/visits-counter/ 时，在 `visits()` 的视图函数中执行 `else` 块的主体并创建一个新的会话。当我们创建一个新会话时，Flask 将会话 `cookie` 发送给客户端。接下来对 `http://localhost:5000/visits-counter/` 的每次的请求(比如刷新时)将执行 `if` 块中的代码，在这个块中更新会话中的访问计数器的值。 修改会话意味着需要创建一个新的 cookie，这就是为什么 Flask 再次将新的会话 cookie 发送给客户端。\r\n",
    "\r\n",
    "要删除会话数据，请访问 http://localhost:5000/delete-visits/。\r\n",
    "`Visits deleted`\r\n",
    "\r\n",
    "如果你现在访问 http://localhost:5000/visits-counter/ ，访问计数器将再次从1开始。\r\n",
    "`Total visits: 1`\r\n",
    "\r\n",
    "默认情况下，会话 `session cookie` 会持续到浏览器关闭为止。若要延长 `session cookie` 的生命周期，请将会话对象的永久属性 `permanent` 设置为 `True。当` permanent 设置为 True 时，会话 cookie 将持续到 `permanent_session_lifetime`。`permanent_session_lifetime` 是 `Flask` 对象的一个属性，类型为 `datetime.timedelta`，默认值为31天。我们可以通过为 `permanent_session_lifetime` 属性指定一个新值或者设置 `permanent_session_lifetime` 配置键来改变这一点。\r\n",
    "\r\n",
    "```python\r\n",
    "import datetime\r\n",
    "\r\n",
    "app = Flask(__name__)\r\n",
    "app.permanent_session_lifetime = datetime.timedelta(days=365) # 属性指定一个新值\r\n",
    "# app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=365) # 设置配置键\r\n",
    "```\r\n",
    "\r\n",
    "就像请求对象一样，会话对象也可以在模板中使用。\r\n",
    "\r\n",
    "##  修改会话数据(Session Data)\r\n",
    " \r\n",
    "注意: 在本节之前，删除本地主机设置的所有 cookie。\r\n",
    "\r\n",
    "大多数情况下，会话对象`session`会自动对其进行修改。但是，在某些情况下，比如对可变数据结构的修改不会自动执行。对于这种情况，必须将会话对象的 modified 属性设置为 True。如果没有将 modified 属性设置为 True, `Flask`则不会将更新后的会话 cookie 发送给客户端。下面的清单演示了如何使用会话对象的修改属性。\r\n",
    "\r\n",
    "打开 main2.py 文件，在 delete_visits()视图函数之前添加以下代码。\r\n",
    "\r\n",
    "flask_app/main2.py\r\n",
    "```python\r\n",
    "#...\r\n",
    "@app.route('/session/')\r\n",
    "def updating_session():\r\n",
    "    res = str(session.items())\r\n",
    "\r\n",
    "    cart_item = {'pineapples': '10', 'apples': '20', 'mangoes': '30'}\r\n",
    "    if 'cart_item' in session:\r\n",
    "        session['cart_item']['pineapples'] = '100'\r\n",
    "        session.modified = True  # 注释掉 将修改后的属性设置为 True 的行来验证这一点\r\n",
    "    else:\r\n",
    "        session['cart_item'] = cart_item\r\n",
    "\r\n",
    "    return res\r\n",
    "#...\r\n",
    "```\r\n",
    "\r\n",
    "第一次访问 http://localhost:5000/session/ 时，执行 `else` 块中的代码并创建一个新会话，其中会话数据是一个字典。随后请求 http://localhost:5000/session/ 通过将菠萝数`'pineapples'`设置为100来更新会话数据。在下一行中，我们将 modified 属性设置为 True，因为如果没有它，Flask 将不会向客户机发送更新的会话 cookie。\r\n",
    "\r\n",
    "如果服务器还没有运行，请启动服务器并访问 http://localhost:5000/session/。\r\n",
    "\r\n",
    "你将显示一个空的会话字典`dict_items([])`，因为浏览器没有会话 cookie 发送到服务器:\r\n",
    "\r\n",
    "再次重新载入页面，你将会看到有10个菠萝的会话字典，如下所示:\r\n",
    "\r\n",
    "`dict_items([('cart_item', {'apples': '20', 'mangoes': '30', 'pineapples': '10'})])`\r\n",
    "\r\n",
    "重新加载第三次页面，你会看到有100个菠萝的会话字典，而不是10如下:\r\n",
    "\r\n",
    "`dict_items([('cart_item', {'apples': '20', 'mangoes': '30', 'pineapples': '100'})])`\r\n",
    "\r\n",
    "会话对象由于修改了属性而选择了这个修改。我们可以通过删除会话 cookie 并注释掉将修改后的属性设置为 True 的行来验证这一点。现在，在第一个请求之后，您将总是显示会话字典与10个菠萝。\r\n",
    "`dict_items([])`\r\n",
    "`dict_items([('cart_item', {'apples': '20', 'mangoes': '30', 'pineapples': '10'})])`\r\n",
    "\r\n",
    "这样就完成了所有你需要知道的关于 Flask 会话的内容。并且不要忘记在 Flask 中默认的会话是客户端会话( client-side sessions)。\r\n",
    "\r\n",
    "补充: 如何删除浏览器中的COOKIES :\r\n",
    "\r\n",
    "![img](delete-local-seesion-cookies.png)"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "orig_nbformat": 4,
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}